/* Copyright (C) 2009, 2011 Christofer Jonsson
 *
 * This file is part of FXH.
 *
 * FXH is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * FXH is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
 * License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with FXH.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "fxh_global.h"
#include "fxh_error.h"
#include "fxh_socket.h"
#include "fxh_net.h"
#include "fxh_version.h"
#include "fxh_util.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>

/* Should contain all flags */
static const char options[] = "p:a:v";

#define FLAG_PORT    'p'
#define FLAG_ADDRESS 'a'
#define FLAG_VERSION 'v'

typedef struct
{
   fxh_bool_t is_listener;
   fxh_bool_t is_connector;
   fxh_bool_t is_sender;
   fxh_bool_t is_receiver;
   fxh_bool_t print_version;
   const char *ip;
   const char *port;
   char **files;
   int nfiles;
} settings_t;

static void parse_input(settings_t *settings, int argc, char **argv);
static void parse_input_error(int option);
static void validate_input(const settings_t *settings);
static fxh_bool_t duplicate_filenames(const settings_t *settings);
static fxh_bool_t files_exist(const settings_t *settings);
static const char* get_basename(const char *path);
static void listen_for_peer(fxh_socket_t *host, fxh_socket_t *peer, const settings_t *settings);
static void connect_to_peer(fxh_socket_t *peer, const settings_t *settings);
static void send_files(fxh_socket_t *peer, settings_t *settings);
static void recv_files(fxh_socket_t *peer);

int main(int argc, char **argv)
{
   fxh_socket_t host, peer;
   settings_t settings = {
      FXH_FALSE, FXH_FALSE, FXH_FALSE, FXH_FALSE, FXH_FALSE,
      NULL, NULL, NULL, 0 };

   parse_input(&settings, argc, argv);
   validate_input(&settings);

   if (settings.print_version)
   {
      printf("%s\n", FXH_VERSION_STR);
      exit(EXIT_SUCCESS);
   }

   /* Enable encryption*/
   fxh_socket_enable(FXH_SOCKET_ENCRYPTION);

   if (settings.is_listener)
   {
      listen_for_peer(&host, &peer, &settings);
   }
   else
   {
      connect_to_peer(&peer, &settings);
   }

   if (settings.is_sender)
   {
      send_files(&peer, &settings);
   }
   else
   {
      recv_files(&peer);
   }

   if (fxh_socket_close(&peer) == FXH_FAILURE)
   {
      FXH_ERROR("Unable to close peer socket");
      exit(EXIT_FAILURE);
   }

   return 0;
}

static void parse_input(settings_t *settings, int argc, char **argv)
{
   int option;
   opterr = 0;

   while ((option = getopt(argc, argv, options)) != -1)
   {
      switch (option)
      {
         case FLAG_PORT:
            settings->port = optarg;
            break;

         case FLAG_ADDRESS:
            settings->ip = optarg;
            break;

         case FLAG_VERSION:
            settings->print_version = FXH_TRUE;
            break;

         case '?':
            parse_input_error(optopt);
            exit(EXIT_FAILURE);

         default:
            exit(EXIT_FAILURE);
      }
   }

   if (optind < argc)
   {
      settings->files = argv + optind;
      settings->nfiles = argc - optind;
   }

   if (settings->ip == NULL)
      settings->is_listener = FXH_TRUE;
   else
      settings->is_connector = FXH_TRUE;

   if (settings->nfiles > 0)
      settings->is_sender = FXH_TRUE;
   else
      settings->is_receiver = FXH_TRUE;
}

static void parse_input_error(int option)
{
   if (option == FLAG_ADDRESS)
   {
      FXH_ERROR("Option -%c requires an argument (ip address)", option);
   }
   else if (option == FLAG_PORT)
   {
      FXH_ERROR("Option -%c requires an argument (port)", option);
   }
   else if (isprint(option))
   {
      FXH_ERROR("Unknown option: -%c", option);
   }
   else
   {
      FXH_ERROR("Unknown option found");
   }
}

static void validate_input(const settings_t *settings)
{
   if (settings->print_version == FXH_TRUE)
   {
      return;
   }

   if (settings->port == NULL)
   {
      FXH_ERROR("Port has not been specified");
      exit(EXIT_FAILURE);
   }

   if (duplicate_filenames(settings) == FXH_TRUE)
   {
      exit(EXIT_FAILURE);
   }

   if (settings->nfiles > 0 && files_exist(settings) == FXH_FALSE)
   {
      exit(EXIT_FAILURE);
   }
}

static fxh_bool_t duplicate_filenames(const settings_t *settings)
{
   int i = 0, j = 0;
   const char *fn1 = NULL;
   const char *fn2 = NULL;

   for (i = 0; i < settings->nfiles; ++i)
   {
      fn1 = get_basename(settings->files[i]);

      for (j = i + 1; j < settings->nfiles; ++j)
      {
         fn2 = get_basename(settings->files[j]);

         if (strcmp(fn1, fn2) == 0)
         {
            FXH_ERROR("Duplicate filename: %s", fn1);
            return FXH_TRUE;
         }
      }
   }

   return FXH_FALSE;
}

static fxh_bool_t files_exist(const settings_t *settings)
{
   FILE *file = NULL;
   int i = 0;

   for (i = 0; i < settings->nfiles; ++i)
   {
      file = fopen(settings->files[i], "r");

      if (file == NULL)
      {
         FXH_ERROR("File not found: %s", settings->files[i]);
         return FXH_FALSE;
      }

      (void)fclose(file);
   }

   return FXH_TRUE;
}

static const char* get_basename(const char *path)
{
   size_t off = 0;

   if (fxh_util_basename(path, strlen(path), &off) == FXH_FAILURE)
   {
      FXH_ERROR("Unable to find filename in path: %s", path);
      exit(EXIT_FAILURE);
   }

   return path + off;
}

static void listen_for_peer(fxh_socket_t *host, fxh_socket_t *peer, const settings_t *settings)
{
   if (fxh_socket_listen(host, settings->port, 1) == FXH_FAILURE)
   {
      FXH_ERROR("Unable to listen for peer");
      exit(EXIT_FAILURE);
   }

   if (fxh_socket_accept(host, peer) == FXH_FAILURE)
   {
      FXH_ERROR("Unable to accept peer connection");
      exit(EXIT_FAILURE);
   }

   if (fxh_socket_close(host) == FXH_FAILURE)
   {
      FXH_ERROR("Unable to close host socket");
      exit(EXIT_FAILURE);
   }
}

static void connect_to_peer(fxh_socket_t *peer, const settings_t *settings)
{
   if (fxh_socket_connect(peer, settings->ip, settings->port) == FXH_FAILURE)
   {
      FXH_ERROR("Unable to connect to peer");
      exit(EXIT_FAILURE);
   }
}

static void send_files(fxh_socket_t *peer, settings_t *settings)
{
   if (fxh_net_send_files(peer, settings->nfiles, settings->files) == FXH_FAILURE)
   {
      FXH_ERROR("Failed to send files");
      (void)fxh_socket_close(peer);
      exit(EXIT_FAILURE);
   }
}

static void recv_files(fxh_socket_t *peer)
{
   if (fxh_net_recv_files(peer) == FXH_FAILURE)
   {
      FXH_ERROR("Failed to receive files");
      (void)fxh_socket_close(peer);
      exit(EXIT_FAILURE);
   }
}
